/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */
package map;

import java.util.Random;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;

import units.Player;
import units.Unit;
//import units.monster.Monster;

public class OtherObject extends MapEntity {

    /**
     *
     */
    private static final long serialVersionUID = 1L;
    // default speed of the unit
    public static float DEFAULT_SPEED = 0.025f;
    // the time for the idling action to occur
    public static int IDLE_TIME = 7000;
    // the visiblity range
    public static int VISIBLITY = 150;
    // the animation frame interval
    protected final static int FRAME_FAST_INTERVAL = 100;
    // the animation frame interval
    protected final static int FRAME_SLOW_INTERVAL = 150;

    public enum TravelType {

        FIXED, GROUND, AIR, WATER
    }

    public enum Direction {

        LEFT, RIGHT
    }

    public enum ObjectType {

        BIRD, FISH, WOLF, WINDMILL_LEFT, WINDMILL_RIGHT, FIRE1, FIRE2, CIRCLE, ROCKS, CHEST, FORCE
    }
    private Animation disp, left, right, idle, destroy, using;	// the display image/animation
    private int id;												// a universal unique id for each unit.
    private int idleTimer;						// width and height of the images.
    private float angle;							// position and speed of the unit
    private boolean displayed, explodable, destroyed;			// whether the unit is alive, teleporting, attacking
    private boolean usable, oneSided, block;				// can be used by the player
    private boolean disappearing;
    private Direction direction;								// the direction which the unit is looking
    private TravelType travelType;								// the travel type of the unit, whether it is in water, air or ground
    private ObjectType objectType;								// the different kinds of objects
    private Unit owningUnit;
    private static Random rnd;									// random number generator
    protected static int objectsCount = 0;

    public OtherObject(ObjectType objectType)
            throws SlickException {

        this(objectType, 0);

    }

    public OtherObject(ObjectType objectType, float angle)
            throws SlickException {

        super(null);

        rnd = new Random();
        this.setId(objectsCount++);
        this.setSpeed(DEFAULT_SPEED);
        this.setObjectType(objectType);
        this.setAngle(angle);
        this.setDisplayed(true);
        this.setIdleTimer(0);
        this.setExplodable(false);
        this.setDestroyed(false);
        this.setOnMap(true);
        this.setOneSided(true);
        this.setBlocking(false);
        this.setDisappearing(false);

        String dirPath = "";

        switch (objectType) {
            case BIRD:
                dirPath = "assets/objects/bird/";
                this.setName("Bird");
                this.setTravelType(TravelType.AIR);
                this.setWidth(25);
                this.setHeight(25);
                this.setOneSided(false);
                this.setBirdAngle();
                break;
            case FISH:
                dirPath = "assets/objects/fish/";
                this.setName("Fish");
                this.setTravelType(TravelType.WATER);
                this.setWidth(50);
                this.setHeight(50);
                this.setOneSided(false);
                break;
            case WOLF:
                dirPath = "assets/objects/wolf/";
                this.setName("Wolf");
                this.setTravelType(TravelType.GROUND);
                this.setWidth(50);
                this.setHeight(50);
                this.setOneSided(false);
                break;
            case WINDMILL_RIGHT:
            case WINDMILL_LEFT:
                dirPath = "assets/objects/windmill/";
                this.setSpeed(0);
                this.setName("Windmill");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(72);
                this.setHeight(72);
                this.setOneSided(false);
                this.setBlocking(true);
                break;
            case CIRCLE:
                dirPath = "assets/objects/circle/";
                this.setSpeed(0);
                this.setName("Circle");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(72);
                this.setHeight(36);
                break;
            case FORCE:
                dirPath = "assets/objects/force/";
                this.setSpeed(0);
                this.setName("Force Field");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(100);
                this.setHeight(80);
                this.setUsable(true);
                this.setBlocking(true);
//				this.setOwningUnit(Monster.getLeader());
                break;
            case FIRE1:
                dirPath = "assets/objects/fire1/";
                this.setSpeed(0);
                this.setName("Fire");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(72);
                this.setHeight(72);
                this.setBlocking(true);
                break;
            case FIRE2:
                dirPath = "assets/objects/fire2/";
                this.setSpeed(0);
                this.setName("Fire");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(72);
                this.setHeight(72);
                this.setBlocking(true);
                break;
            case ROCKS:
                dirPath = "assets/objects/rocks/";
                this.setSpeed(0);
                this.setName("Rocks");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(167);
                this.setHeight(100);
                this.setExplodable(true);
                this.setBlocking(true);
                break;
            case CHEST:
                dirPath = "assets/objects/chest/";
                this.setSpeed(0);
                this.setName("Chest");
                this.setTravelType(TravelType.FIXED);
                this.setWidth(72);
                this.setHeight(36);
                this.setUsable(true);
                this.setBlocking(true);
                break;
        }

        this.loadImages(dirPath, (int) this.getWidth(), (int) this.getHeight());

        switch (objectType) {

            case BIRD:
                this.lookInDefaultDirection();
                break;

            case FISH:
            case WOLF:
                this.lookInRandomDirection();
                break;

            case WINDMILL_RIGHT:
                this.lookRight();
                break;

            default:
                this.lookLeft();
                break;

        }

    }

    /** Loads all the images for this unit.
     * @param dirPath The path of the directory where the images are in (including the following "/").
     * @param width The width of a sprite image.
     * @param height The height of the sprite image.
     * @throws SlickException
     */
    private void loadImages(String dirPath, int width, int height)
            throws SlickException {

        Image img = new Image(dirPath + "left.png");
        this.setLeftAnimation(img, width, height);

        if (!this.isOneSided()) {
            this.setRightAnimation(img.getFlippedCopy(true, false), width, height);
        }

        if (this.getTravelType() == TravelType.GROUND || this.getTravelType() == TravelType.WATER) {
            this.setIdlingAnimation(new Image(dirPath + "idle.png"), width, height);
        }

        if (this.isExplodable()) {
            this.setDestroyAnimation(new Image(dirPath + "destroy.png"), width, height);
        }

        if (this.isUsable()) {
            this.setUseAnimation(new Image(dirPath + "use.png"), width, height);
        }

    }

    /** Update the player's state for a frame.
     * @param world A reference to the world.
     * @param delta Time passed since last frame (milliseconds).
     */
    public void update(World world, int delta)
            throws SlickException {

        if (this.isDisplayed()) {

            Player player = world.getPlayer();

            if (this.getObjectType() == ObjectType.CIRCLE) {

                if (player.isAlive() && !player.isTeleporting()) {
                    double dist2 = Unit.calculateDistanceSqrd(player.getX(), player.getY(), this.getX(), this.getY());
                    if (dist2 <= Unit.Squared(Unit.CLOSE_PROXIMITY)) {
                        // TODO
                        world.showAllAllies(1);
                        this.setDisplayed(false);
                    }
                }

            } else if (this.getObjectType() == ObjectType.CHEST) {

                if (player.isAlive() && !player.isTeleporting()) {
                    double dist2 = Unit.calculateDistanceSqrd(player.getX(), player.getY(), this.getX(), this.getY());
                    if (dist2 <= Unit.Squared(Unit.CLOSE_PROXIMITY)) {
                        this.setDisplayAnimation(this.getUseAnimation());
                    }
                }

            } else if (this.getObjectType() == ObjectType.ROCKS) {

                if (this.isDestroyed() && this.getDestroyAnimation().isStopped()) {
                    this.setOnMap(false);
                }

            } else if (this.getObjectType() == ObjectType.WOLF) {

                if (player.isAlive() && !player.isTeleporting()) {
                    double dist2 = Unit.calculateDistanceSqrd(player.getX(), player.getY(), this.getX(), this.getY());
                    if (dist2 <= Unit.Squared(VISIBLITY)) {
                        double distx = player.getX() - this.getX();
                        if (distx < 0) {
                            this.lookLeft();
                        } else {
                            this.lookRight();
                        }
                    } else {
                        this.idle(delta);
                    }
                }

            } else if (this.getObjectType() == ObjectType.FISH) {

                this.idle(delta);

            } else if (this.getObjectType() == ObjectType.FORCE) {

                if (!this.isDisappearing() && this.getOwningUnit() != null) {
                    if (!this.getOwningUnit().isAlive()) {
                        this.disappear();
                    }
                } else if (this.isDisappearing()) {
                    if (this.getDisplayAnimation().isStopped()) {
                        this.setOnMap(false);
                    }
                }

            }

            if (this.getTravelType() == TravelType.AIR) {
                float x = (float) (this.getX() - this.getSpeed() * Math.sin(this.getAngle()) * delta);
                float y = (float) (this.getY() - this.getSpeed() * Math.cos(this.getAngle()) * delta);
                this.moveTo(x, y);
                if (!world.isInsideMap(this.getX(), this.getY(), this.getWidth(), this.getHeight())) {
                    //this.setAngle(this.getAngle()+Math.PI/4);
                    this.setBirdAngle();
                    this.moveToRandomPos(world);
                }
            }

            this.getDisplayAnimation().update(delta);

        }
    }

    /** Render the entire screen, so it reflects the current game state.
     * @param g The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @param cameraOffsetY The number of pixels the world has been offset to in the x-direction (pixels).
     */
    public void render(Graphics g, float cameraOffsetX, float cameraOffsetY) {

        if (this.isDisplayed()) {
            this.getDisplayAnimation().draw((float) (this.getX() - cameraOffsetX - this.getWidth() / 2), (float) (this.getY() - cameraOffsetY - this.getHeight() / 2));
        }

    }

    /** Make the unit display the idling animation.
     * @param delta The time passed since the previous frame update (milliseconds).
     */
    private void idle(int delta) {

        if (this.getTravelType() == TravelType.GROUND || this.getTravelType() == TravelType.WATER) {

            this.setIdleTimer(this.getIdleTimer() + delta);
            int TIME = IDLE_TIME + (Math.abs(getRandomInt()) % IDLE_TIME) * 1000;
            if (this.getIdleTimer() >= TIME) {
                this.setIdleTimer(0);
                this.setDisplayAnimation(this.getIdlingAnimation());
                this.getIdlingAnimation().restart();
            }
            boolean endIdle = (!this.getIdlingAnimation().isStopped() && this.getIdlingAnimation().getFrame() == this.getIdlingAnimation().getFrameCount() - 1);

            if (endIdle) {
                this.setIdleTimer(0);
                this.getIdlingAnimation().stop();
                this.lookInRandomDirection();
            }

        }

    }

    /** Turn the object to the left.
     */
    private void lookLeft() {
        this.setDisplayAnimation(this.getLeftAnimation());
        this.setDirection(Direction.LEFT);
    }

    /** Turn the object to the right.
     */
    private void lookRight() {
        this.setDisplayAnimation(this.getRightAnimation());
        this.setDirection(Direction.RIGHT);
    }

    /** Turn the unit to the right or left randomly.
     */
    private void lookInRandomDirection() {
        if ((Math.abs(getRandomInt()) % 2) == 0) {
            this.lookLeft();
        } else {
            this.lookRight();
        }
    }

    /** Turn the unit to the right or left randomly.
     */
    private void lookInDefaultDirection() {
        if (this.getDirection() == Direction.LEFT) {
            this.lookLeft();
        } else {
            this.lookRight();
        }
    }

    /* Getters and Setters */
    private Animation getDisplayAnimation() {
        return this.disp;
    }

    private void setDisplayAnimation(Animation disp) {
        this.disp = disp;
    }

    private Animation getLeftAnimation() {
        return this.left;
    }

    private void setLeftAnimation(Image image, int width, int height) {
        this.left = new Animation(new SpriteSheet(image, width, height), FRAME_FAST_INTERVAL);
        this.left.setLooping(true);
        this.left.setAutoUpdate(false);
    }

    private Animation getRightAnimation() {
        return this.right;
    }

    private void setRightAnimation(Image image, int width, int height) {
        this.right = new Animation(new SpriteSheet(image, width, height), FRAME_FAST_INTERVAL);
        this.right.setLooping(true);
        this.right.setAutoUpdate(false);
    }

    private Animation getIdlingAnimation() {
        return this.idle;
    }

    private void setIdlingAnimation(Image image, int width, int height) {
        this.idle = new Animation(new SpriteSheet(image, width, height), FRAME_FAST_INTERVAL);
        this.idle.setLooping(true);
        this.idle.setAutoUpdate(false);
    }

    public int getId() {
        return id;
    }

    private void setId(int id) {
        this.id = id;
    }

    public float getAngle() {
        return this.angle;
    }

    private void setAngle(float angle) {
        this.angle = (float) (angle % (2 * Math.PI));
    }

    private boolean isDisplayed() {
        return this.displayed;
    }

    public void setDisplayed(boolean displayed) {
        this.displayed = displayed;
    }

    public Direction getDirection() {
        return this.direction;
    }

    private void setDirection(Direction direction) {
        this.direction = direction;
    }

    public TravelType getTravelType() {
        return this.travelType;
    }

    private void setTravelType(TravelType type) {
        this.travelType = type;
    }

    public ObjectType getObjectType() {
        return this.objectType;
    }

    public void setObjectType(ObjectType type) {
        this.objectType = type;
    }

    private static int getRandomInt() {
        return rnd.nextInt();
    }

    /** Get the timer value of idling.
     * @return The dialog timer value.
     */
    private int getIdleTimer() {
        return this.idleTimer;
    }

    /** Set the timer value of idling.
     * @param idlingTimer The idling timer value.
     */
    private void setIdleTimer(int idlingTimer) {
        this.idleTimer = idlingTimer;
    }

    public boolean isExplodable() {
        return this.explodable;
    }

    public void setExplodable(boolean explodable) {
        this.explodable = explodable;
    }

    public boolean isDestroyed() {
        return this.destroyed;
    }

    public void setDestroyed(boolean destroyed) {
        this.destroyed = destroyed;
    }

    public void destroy() {
        this.setBlocking(false);
        this.setDestroyed(true);
        this.getDestroyAnimation().restart();
        this.setDisplayAnimation(this.getDestroyAnimation());
    }

    /** Checks if a given point on the map is occupied by this object.
     * @param x The x coordinate.
     * @param y The y coordinate.
     */
    public boolean isOccupied(float x, float y) {
        boolean dirx = (x >= this.getMinX());
        dirx &= (x <= this.getMaxX());
        boolean diry = (y >= this.getMinY());
        diry &= (y <= this.getMaxY());
        return dirx && diry;
    }

    @Override
    public void setOnMap(boolean onMap) {
        super.setOnMap(onMap);
        this.setDisplayed(this.isDisplayed() && this.isOnMap());
    }

    public boolean isUsable() {
        return this.usable;
    }

    private void setUsable(boolean usable) {
        this.usable = usable;
    }

    private Animation getDestroyAnimation() {
        return this.destroy;
    }

    private void setDestroyAnimation(Image image, int width, int height) {
        this.destroy = new Animation(new SpriteSheet(image, width, height), FRAME_FAST_INTERVAL);
        ;
        this.destroy.setLooping(false);
        this.destroy.setAutoUpdate(false);
    }

    private Animation getUseAnimation() {
        return this.using;
    }

    private void setUseAnimation(Image image, int width, int height) {
        this.using = new Animation(new SpriteSheet(image, width, height), FRAME_FAST_INTERVAL);
        ;
        this.using.setLooping(false);
        this.using.setAutoUpdate(false);
    }

    /** Move the object to a point, but does NOT check for collision detection.
     * @param x The X coordinate (pixels).
     * @param y The Y coordinate (pixels).
     */
    public void moveTo(float x, float y) {
        this.setX(x);
        this.setY(y);
    }

    private boolean isOneSided() {
        return this.oneSided;
    }

    private void setOneSided(boolean oneSided) {
        this.oneSided = oneSided;
    }

    public boolean isBlocking() {
        return this.block;
    }

    public void setBlocking(boolean block) {
        this.block = block;
    }

    private void setBirdAngle() {

        int offset = 2 + Math.abs(getRandomInt()) % 3;
        int mul = (Math.abs(getRandomInt()) % 2 == 0) ? 1 : -1;
        float angle = (float) (mul * Math.PI / offset);
        this.setAngle(angle);
        if (this.getAngle() > 0) {
            this.setDirection(Direction.LEFT);
        } else {
            this.setDirection(Direction.RIGHT);
        }
    }

    public Unit getOwningUnit() {
        return this.owningUnit;
    }

    public void setOwningUnit(Unit owningUnit) {
        this.owningUnit = owningUnit;
    }

    public void disappear() {
        // TODO
        //this.setDisappearing(true);
        this.setOnMap(false);
    }

    private boolean isDisappearing() {
        return this.disappearing;
    }

    private void setDisappearing(boolean disappearing) {
        this.disappearing = disappearing;
    }

    private void moveToRandomPos(World world) {
        int width = world.getMap().getWidth() * world.getMap().getTileWidth();
        int height = world.getMap().getHeight() * world.getMap().getTileHeight();
        float x = Math.abs(getRandomInt()) % width;
        float y = Math.abs(getRandomInt()) % height;
        while (world.isOnScreen(x, y, this.getWidth(), this.getHeight())) {
            x = Math.abs(getRandomInt()) % width;
            y = Math.abs(getRandomInt()) % height;
        }
        this.moveTo(x, y);
        this.lookInDefaultDirection();
    }
}
